#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
Created on 2018年11月01日
@author: Irony
@site: https://pyqt5.com, https://github.com/892768447
@email: 892768447@qq.com
@file: Translate
@description: 谷歌翻译
"""
from calendar import timegm
import json
from math import floor
import re
from time import gmtime
from urllib.parse import urlencode

from tornado.gen import coroutine, Task
from tornado.httpclient import AsyncHTTPClient
from tornado.ioloop import IOLoop


__Author__ = """By: Irony
QQ: 892768447
Email: 892768447@qq.com"""
__Copyright__ = 'Copyright (c) 2018 Irony'
__Version__ = 1.0


class Translate:

    SALT_1 = "+-a^+6"
    SALT_2 = "+-3^+b+-f"
    seed = None

    @classmethod
    @coroutine
    def getSeed(cls):
        if cls.seed:
            return cls.seed
        client = AsyncHTTPClient()
        resp = yield Task(
            client.fetch,
            'https://translate.google.cn/',
            method='GET'
        )
        if not resp.body:
            return
        line = resp.body.decode(
            encoding='utf-8', errors='ignore').split('\n')[-1]
        tkk_expr = re.search(".*?(TKK=.*?;)W.*?", line).group(1)

        try:
            # Grab the token directly if already generated by function call
            cls.seed = re.search("\d{6}\.[0-9]+", tkk_expr).group(0)
        except AttributeError:
            # Generate the token using algorithm
            timestamp = timegm(gmtime())
            hours = int(floor(timestamp / 3600))
            a = re.search("a\\\\x3d(-?\d+);", tkk_expr).group(1)
            b = re.search("b\\\\x3d(-?\d+);", tkk_expr).group(1)

            cls.seed = str(hours) + "." + str(int(a) + int(b))

        return cls.seed

    @classmethod
    @coroutine
    def translate(cls, text):
        # 翻译
        client = AsyncHTTPClient()
        body = urlencode({
            'client': 't',
            'sl': 'auto',
            'tl': 'zh-CN',
            'hl': 'zh-CN',
            'dt': 'at',
            'dt': 'bd',
            'dt': 'ex',
            'dt': 'ld',
            'dt': 'md',
            'dt': 'qca',
            'dt': 'rw',
            'dt': 'rm',
            'dt': 'ss',
            'dt': 't',
            'ie': 'UTF-8',
            'oe': 'UTF-8',
            'ssel': '3',
            'tsel': '3',
            'kc': '0',
            'tk': cls.getTk(text),
            'q': text
        }, safe='()')
        # ?client=t&sl=auto&tl=zh-CN&hl=zh-CN&dt=at&dt=bd&dt=ex&dt=ld&dt=md&dt=qca&dt=rw&dt=rm&dt=ss&dt=t&ie=UTF-8&oe=UTF-8&source=btn&ssel=0&tsel=0&kc=0&tk={}&q={}'
        url = 'https://translate.google.cn/translate_a/single'
        resp = yield Task(
            client.fetch,
            url,
            method='POST',
            body=body,
            headers={
                'Accept': '*/*',
                'Accept-Language': 'zh-CN,zh;q=0.9',
                'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
                'Referer': 'https://translate.google.cn/',
                'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.6735.400 QQBrowser/10.2.2614.400'
            }
        )
        if not resp.body:
            result = '没有结果'
        else:
            try:
                result = ''
                for ret in json.loads(resp.body.decode())[0]:
                    result += ret[0]
            except Exception as e:
                result = '翻译错误: ' + str(e)

        return result

    @classmethod
    def getTk(cls, text):
        if not cls.seed:
            raise Exception('seed is None')

        first_seed, second_seed = cls.seed.split(".")

        try:
            d = bytearray(text.encode('UTF-8'))
        except UnicodeDecodeError:
            # This will probably only occur when d is actually a str containing UTF-8 chars, which means we don't need
            # to encode.
            d = bytearray(text)

        a = int(first_seed)
        for value in d:
            a += value
            a = cls._work_token(a, cls.SALT_1)
        a = cls._work_token(a, cls.SALT_2)
        a ^= int(second_seed)
        if 0 > a:
            a = (a & 2147483647) + 2147483648
        a %= 1E6
        a = int(a)
        return str(a) + "." + str(a ^ int(first_seed))

    """ Functions used by the token calculation algorithm """
    @classmethod
    def _rshift(cls, val, n):
        return val >> n if val >= 0 else (val + 0x100000000) >> n

    @classmethod
    def _work_token(cls, a, seed):
        for i in range(0, len(seed) - 2, 3):
            char = seed[i + 2]
            d = ord(char[0]) - 87 if char >= "a" else int(char)
            d = cls._rshift(a, d) if seed[i + 1] == "+" else a << d
            a = a + d & 4294967295 if seed[i] == "+" else a ^ d
        return a


def _test_seed():
    response = requests.get("https://translate.google.cn/")
    line = response.text.split('\n')[-1]
    tkk_expr = re.search(".*?(TKK=.*?;)W.*?", line).group(1)

    try:
        # Grab the token directly if already generated by function call
        result = re.search("\d{6}\.[0-9]+", tkk_expr).group(0)
    except AttributeError:
        # Generate the token using algorithm
        timestamp = timegm(gmtime())
        hours = int(floor(timestamp / 3600))
        a = re.search("a\\\\x3d(-?\d+);", tkk_expr).group(1)
        b = re.search("b\\\\x3d(-?\d+);", tkk_expr).group(1)

        result = str(hours) + "." + str(int(a) + int(b))

    return result


if __name__ == '__main__':
    import requests
    print(_test_seed())
    print(IOLoop.current().run_sync(lambda: Translate.getSeed()))
    print(IOLoop.current().run_sync(lambda: Translate.getSeed()))
    print(IOLoop.current().run_sync(lambda: Translate.translate(
        'Non-window widgets are child widgets, displayed within their parent widgets. Most widgets in Qt are mainly useful as child widgets. For example, it is possible to display a button as a top-level window, but most people prefer to put their buttons inside other widgets, such as QDialog.')))
